/*
 * plugin-manager.c
 *
 * Copyright (C) 2015 Intel Corporation
 *
 * cve-check-tool is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 */

#define _GNU_SOURCE
#include "config.h"

#include <dirent.h>
#include <dlfcn.h>
#include <errno.h>
#include <stdbool.h>
#include <sys/types.h>

#include "plugin-manager.h"
#include "plugin.h"
#include "util.h"

static CveHashmap *_plugins;

static char *get_plugins_path(void)
{
#ifdef TEST_SUITE_BUILD
        return strdup(TOP_BUILD_DIR "/tests/dummy_install");
#elif defined(INTREE)
        return strdup(TOP_BUILD_DIR "/.libs/");
#elif !defined(RELATIVE_PLUGINS)
        /* Return built in path when relative plugins disabled */
        return strdup(MODULE_DIR);
#endif
        autofree(char) *binname = NULL;

        binname = realpath("/proc/self/exe", NULL);

        if (!binname) {
                return strdup(MODULE_DIR);
        }

        /* relative plugins */
        const char *libpaths[] = { "lib/cve-check-tool", "lib64/cve-check-tool" };

        autofree(char) *cp = strdup(binname);
        if (!cp) {
                return NULL;
        }
        char *d = dirname(cp);
        if (!d) {
                return NULL;
        }

        for (uint i = 0; i < sizeof(libpaths) / sizeof(libpaths[0]); i++) {
                autofree(char) *pot = NULL;

                if (!asprintf(&pot, "%s/../%s", d, libpaths[i])) {
                        fprintf(stderr, "get_plugins_path(): Out of memory\n");
                        abort();
                }
                if (access(pot, F_OK) == 0) {
                        return strdup(pot);
                }
        }

        /* Still didn't find it, return baked in path */
        return strdup(MODULE_DIR);
}

static char *build_module_path(const char *name)
{
        autofree(char) *dir = get_plugins_path();
        char *p = NULL;

        if (!dir) {
                fprintf(stderr, "build_module_path(): Failed to determine module path!\n");
                return NULL;
        }

        if (!asprintf(&p, "%s/%s", dir, name)) {
                fprintf(stderr, "build_module_path(): Out of memory\n");
                return NULL;
        }

        return p;
}

static void load_module(const char *name)
{
        void *handle, *cast;
        autofree(char) *path = NULL;
        char *error = NULL;
        cve_plugin_init init_func;
        CvePlugin *plugin = NULL;

        path = build_module_path(name);
        if (!path) {
                fprintf(stderr, "Out of memory\n");
                return;
        }
        handle = dlopen(path, RTLD_LAZY);
        if (!handle) {
                fprintf(stderr, "Unable to load module: %s\n", dlerror());
                return;
        }
        dlerror();
        cast = dlsym(handle, "cve_plugin_module_init");
        if (!cast || (error = dlerror()) != NULL) {
                fprintf(stderr, "Cannot load module: %s\n", error);
                abort();
        }
        dlerror();
        memcpy(&init_func, &cast, sizeof(init_func));

        plugin = calloc(1, sizeof(struct CvePlugin));
        if (!plugin) {
                fprintf(stderr, "Unable to allocate memory\n");
                abort();
        }
        if (!init_func(plugin)) {
                fprintf(stderr, "Plugin initialisation failed\n");
                abort();
        }
        if (!plugin->name) {
                fprintf(stderr, "Plugin %s does not set a name - aborting\n", name);
                abort();
        }
        plugin->handle = handle;
        cve_hashmap_put(_plugins, (char *)plugin->name, plugin);
}

static void destroy_plugin(CvePlugin *plugin)
{
        if (plugin->destroy) {
                plugin->destroy(plugin);
        }
        if (plugin->handle) {
                dlclose(plugin->handle);
        }
        free(plugin);
}

void cve_plugin_manager_init()
{
        DIR *dir = NULL;
        struct dirent *ent = NULL;
        autofree(char) *mod_path = NULL;

        if (_plugins) {
                return;
        }
        _plugins = cve_hashmap_new_full(string_hash, string_compare, NULL, (hash_free_func)destroy_plugin);

        mod_path = get_plugins_path();

        if (!mod_path) {
                fprintf(stderr, "Unable to determine module path\n");
                return;
        }
        printf("cve_plugin_manager_init: mod_path = %s\n",mod_path);

        if (!(dir = opendir(mod_path))) {
                fprintf(stderr, "Unable to list modules: %s for mod_path %s\n", strerror(errno),mod_path);
                return;
        }

        while ((ent = readdir(dir))) {
                if (g_str_has_suffix(ent->d_name, ".so")) {
                        load_module(ent->d_name);
                }
        }
        if (dir) {
                closedir(dir);
        }
}

void cve_plugin_manager_destroy()
{
        if (!_plugins) {
                return;
        }
        cve_hashmap_free(_plugins);
        _plugins = NULL;
}

CvePlugin *cve_plugin_get_by_name(const char *name)
{
        CvePlugin *ret = NULL;

        if (!_plugins) {
                return NULL;
        }
        ret = cve_hashmap_get(_plugins, name);
        return ret;
}

GList *cve_plugin_get_by_cap(int cap)
{
        GList *ret = NULL;
        CveHashmapIter iter;
        CvePlugin *plug = NULL;

        if (!_plugins) {
                return NULL;
        }

        cve_hashmap_iter_init(_plugins, &iter);
        while (cve_hashmap_iter_next(&iter, NULL, (void **)&plug)) {
                if (plug->flags & cap) {
                        ret = g_list_append(ret, plug);
                }
        }
        return ret;
}

/*
 * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
 *
 * Local variables:
 * c-basic-offset: 8
 * tab-width: 8
 * indent-tabs-mode: nil
 * End:
 *
 * vi: set shiftwidth=8 tabstop=8 expandtab:
 * :indentSize=8:tabSize=8:noTabs=true:
 */
